package net.maku.framework.IPLimiter.aspect;

/**
 * @projectName: maku-boot
 * @package: net.maku.framework.IPLimiter.aspect
 * @className: IpLimterHandler
 * @author: 字杰
 * @description: TODO
 * @date: 2024/2/27 12:51
 * @version: 1.0
 */

import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import net.maku.framework.IPLimiter.annotations.IpLimiter;
import net.maku.framework.common.cache.RedisKeys;
import net.maku.framework.common.utils.DateUtils;
import net.maku.framework.common.utils.IpUtils;
import net.maku.framework.common.utils.Result;
import net.maku.framework.security.user.SecurityUser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Aspect
@Component
@Slf4j
public class IpLimterHandler {


    @Autowired
    StringRedisTemplate redisTemplate;


    /**
     * getRedisScript 读取脚本工具类
     * 这里设置为Long,是因为ipLimiter.lua 脚本返回的是数字类型
     */
    private DefaultRedisScript<Long> getRedisScript;

    @PostConstruct
    public void init() {
        getRedisScript = new DefaultRedisScript<>();
        getRedisScript.setResultType(Long.class);
        getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("ipLimiter.lua")));
        log.info("IpLimterHandler[分布式限流处理器]脚本加载完成");
    }

    /**
     * 这个切点可以不要，因为下面的本身就是个注解
     */
//    @Pointcut("@annotation(com.jincou.iplimiter.annotation.IpLimiter)")
//    public void rateLimiter() {}

    /**
     * 如果保留上面这个切点，那么这里可以写成
     * @Around("rateLimiter()&&@annotation(ipLimiter)")
     */
    @Around("@annotation(ipLimiter)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, IpLimiter ipLimiter) throws Throwable {
        if (log.isDebugEnabled()) {
            log.debug("IpLimterHandler[分布式限流处理器]开始执行限流操作");
        }
        String userIp = null;
        String requestURI = null;
        try {
            // 获取请求信息
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            requestURI = request.getRequestURI();
            String requestMethod = request.getMethod();
            String remoteAddr = request.getRemoteAddr();
            // 获取请求用户IP
            userIp = IpUtils.getIpAddr(request);
            if (userIp == null) {
                return Result.error("运行环境存在风险");
            }
        } catch (Exception e) {
            log.error("获取request出错=>" + e.getMessage());
            if (userIp == null) {
                return Result.error("运行环境存在风险");
            }
        }
        Signature signature = proceedingJoinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("the Annotation @IpLimter must used on method!");
        }
        /**
         * 获取注解参数
         */
        // 放行模块IP
        String[] limitIp = ipLimiter.ipAdress();
        int len;
        if (limitIp != null && (len = limitIp.length) != 0) {
            for (int i = 0; i < len; i++) {
                if (limitIp[i].equals(userIp)) {
                    return proceedingJoinPoint.proceed();
                }
            }
        }
        // 限流阈值
        long limitTimes = ipLimiter.limit();
        // 限流超时时间
        long expireTime = ipLimiter.time();
        boolean lockUri = ipLimiter.lockUri();
        if (log.isDebugEnabled()) {
            log.debug("IpLimterHandler[分布式限流处理器]参数值为-limitTimes={},limitTimeout={}", limitTimes, expireTime);
        }
        // 限流提示语
        String message = ipLimiter.message();
        /**
         * 执行Lua脚本
         */
        List<String> ipList = new ArrayList();
        // 设置key值为注解中的值
        if (lockUri) {
            ipList.add(RedisKeys.getIpLimterKey(userIp+requestURI));
        } else {
            ipList.add(userIp);
        }
        /**
         * 调用脚本并执行
         */
        try {
            Object x = redisTemplate.execute(getRedisScript, ipList, expireTime+"", limitTimes+"");
            Long result = (Long) x;
            if (result == 0) {
                Long userId = null;
                try {
                    userId = SecurityUser.getUserId();
                } catch (Exception e) {

                }
                log.info("[分布式限流处理器]限流执行结果-ip={}-接口={}-用户ID={}-result={}-time={},已被限流", userIp,requestURI == null?"未知":requestURI
                        ,userId==null?"用户未登录":userId,result, DateUtils.format(new Date()),DateUtils.DATE_TIME_PATTERN);
                // 达到限流返回给前端信息
                return Result.error(message);
            }
            if (log.isDebugEnabled()) {
                log.debug("IpLimterHandler[分布式限流处理器]限流执行结果-result={},请求[正常]响应", result);
            }
            return proceedingJoinPoint.proceed();
        } catch (Exception e) {
            log.error("限流错误",e);
            return proceedingJoinPoint.proceed();
        }

    }
}
